<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>GoJS Context Menus -- Northwoods Software</title>
  <!-- Copyright 1998-2016 by Northwoods Software Corporation. -->
  <script src="go.js"></script>
  <script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">

<h2>Context Menus</h2>
<p>
<b>GoJS</b> provides a mechanism for you to define context menus for any object or for the diagram background.
</p>
<p>
A context menu is an <a>Adornment</a> that is shown when the user context-clicks (right mouse clicks) an object
that has its <a>GraphObject.contextMenu</a> set.
The context menu is bound to the same data as the part itself.
</p>
<p>
It is typical to have the Adornment be a "Vertical" <a>Panel</a> containing "ContextMenuButton"s,
as you can see in the code below in the assignment of the Node's <a>GraphObject.contextMenu</a> and <a>Diagram.contextMenu</a> properties.
Each "ContextMenuButton" is a Panel on which you can set the <a>GraphObject.click</a> event handler.
In the event handler <code>obj.part</code> will be the whole context menu Adornment.
<code>obj.part.adornedPart</code> will be adorned Node or Link.
The bound data is <code>obj.part.data</code>, which will be the same as <code>obj.part.adornedPart.data</code>.
</p>
<p>
In this example each <a>Node</a> has its <a>GraphObject.contextMenu</a> property set to an Adornment that shows
a single button that when clicked changes the color property of the bound model data.
The diagram gets its own context menu by setting <a>Diagram.contextMenu</a>.
</p>
<pre data-language="javascript" id="contextmenus">
  // This method is called as a context menu button's click handler.
  // Rotate the selected node's color through a predefined sequence of colors.
  function changeColor(e, obj) {
    diagram.startTransaction("changed color");
    // get the context menu that holds the button that was clicked
    var contextmenu = obj.part;
    // get the node data to which the Node is data bound
    var nodedata = contextmenu.data;
    // compute the next color for the node
    var newcolor = "lightblue";
    switch (nodedata.color) {
      case "lightblue": newcolor = "lightgreen"; break;
      case "lightgreen": newcolor = "lightyellow"; break;
      case "lightyellow": newcolor = "orange"; break;
      case "orange": newcolor = "lightblue"; break;
    }
    // modify the node data
    // this evaluates data Bindings and records changes in the UndoManager
    diagram.model.setDataProperty(nodedata, "color", newcolor);
    diagram.commitTransaction("changed color");
  }

  // this is a normal Node template that also has a contextMenu defined for it
  diagram.nodeTemplate =
    $(go.Node, "Auto",
      $(go.Shape, "RoundedRectangle",
        { fill: "white" },
        new go.Binding("fill", "color")),
      $(go.TextBlock, { margin: 5 },
        new go.Binding("text", "key")),
      {
        contextMenu:     // define a context menu for each node
          $(go.Adornment, "Vertical",  // that has one button
            $("ContextMenuButton",
              $(go.TextBlock, "Change Color"),
              { click: changeColor })
            // more ContextMenuButtons would go here
          )  // end Adornment
      }
    );

  // also define a context menu for the diagram's background
  diagram.contextMenu =
    $(go.Adornment, "Vertical",
      $("ContextMenuButton",
        $(go.TextBlock, "Undo"),
        { click: function(e, obj) { e.diagram.commandHandler.undo(); } },
        new go.Binding("visible", "", function(o) {
                                          return o.diagram.commandHandler.canUndo();
                                        }).ofObject()),
      $("ContextMenuButton",
        $(go.TextBlock, "Redo"),
        { click: function(e, obj) { e.diagram.commandHandler.redo(); } },
        new go.Binding("visible", "", function(o) {
                                          return o.diagram.commandHandler.canRedo();
                                        }).ofObject()),
      // no binding, always visible button:
      $("ContextMenuButton",
        $(go.TextBlock, "New Node"),
        { click: function(e, obj) {
          var diagram = e.diagram;
          diagram.startTransaction('new node');
          var data = {};
          diagram.model.addNodeData(data);
          part = diagram.findPartForData(data);
          part.location = diagram.toolManager.contextMenuTool.mouseDownPoint;
          diagram.commitTransaction('new node');
        } })
    );

  diagram.initialContentAlignment = go.Spot.Center;

  var nodeDataArray = [
    { key: "Alpha", color: "lightyellow" },
    { key: "Beta", color: "orange" }
  ];
  var linkDataArray = [
    { from: "Alpha", to: "Beta" }
  ];
  diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
  diagram.undoManager.isEnabled = true;
</pre>
<script>goCode("contextmenus", 350, 200)</script>
<p>
Try context clicking a node and invoking the "Change Color" command a few times.
With the diagram context menu you will be able to "Undo" and/or "Redo", or you can use Control-Z and/or Control-Y.
</p>

<h4>Positioning</h4>
<p>
There are two ways to customize the positioning of the context menu relative to the adorned GraphObject.
One way is to override <a>ContextMenuTool.positionContextMenu</a>.
Another way is to have the context menu <a>Adornment</a> include a <a>Placeholder</a>.
The Placeholder is positioned to have the same size and position as the adorned object.
</p>
<pre data-language="javascript" id="contextmenusplaceholder">
  // this is a shared context menu button click event handler, just for demonstration
  function cmCommand(e, obj) {
    var node = obj.part.adornedPart;  // the Node with the context menu
    var buttontext = obj.elt(1);  // the TextBlock
    alert(buttontext.text + " command on " + node.data.key);
  }

  // this is a normal Node template that also has a contextMenu defined for it
  diagram.nodeTemplate =
    $(go.Node, "Auto",
      $(go.Shape, "RoundedRectangle",
        { fill: "white" },
        new go.Binding("fill", "color")),
      $(go.TextBlock, { margin: 5 },
        new go.Binding("text", "key")),
      {
        contextMenu:                            // define a context menu for each node
          $(go.Adornment, "Spot",               // that has several buttons around
            $(go.Placeholder, { padding: 5 }),  // a Placeholder object
            $("ContextMenuButton", $(go.TextBlock, "Top"),
              { alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom, click: cmCommand }),
            $("ContextMenuButton", $(go.TextBlock, "Right"),
              { alignment: go.Spot.Right, alignmentFocus: go.Spot.Left, click: cmCommand }),
            $("ContextMenuButton", $(go.TextBlock, "Bottom"),
              { alignment: go.Spot.Bottom, alignmentFocus: go.Spot.Top, click: cmCommand }),
            $("ContextMenuButton", $(go.TextBlock, "Left"),
              { alignment: go.Spot.Left, alignmentFocus: go.Spot.Right, click: cmCommand })
          )  // end Adornment
      }
    );

  diagram.initialContentAlignment = go.Spot.Center;
  var nodeDataArray = [
    { key: "Alpha", color: "lightyellow" },
    { key: "Beta", color: "orange" }
  ];
  var linkDataArray = [
    { from: "Alpha", to: "Beta" }
  ];
  diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("contextmenusplaceholder", 350, 200)</script>

<h3>Custom Context Menus</h3>
<p>
It is possible to define custom context menus using HTML instead of Adornments by overriding the
<a>ContextMenuTool.showContextMenu</a> and <a>ContextMenuTool.hideContextMenu</a> methods.
The <a href="../samples/customContextMenu.html">Custom Context Menu sample</a> shows two such custom context menus.
</p>

<h3>Default Context Menu for Touch-enabled devices</h3>
<p>
Touch devices are presumed to have no keyboard ability, which makes actions like copying and pasting more difficult.
Because of this, <b>GoJS</b> provides a built-in default context menu on touch devices, implemented in HTML.
The buttons on this menu are populated dynamically, depending on the target GraphObject (if any) and Diagram and their properties.
</p>
<p>
If you define your own custom context menus, they will prevent the default context menu from appearing on touch devices.
We recommend that your custom context menus include common commands appropriate for your app.
</p>

</div>
</div>
</body>
</html>
